home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / DA.PY < prev    next >
Encoding:
Python Source  |  2000-09-13  |  20.6 KB  |  583 lines

  1. ##############################################################################
  2. # Zope Public License (ZPL) Version 1.0
  3. # -------------------------------------
  4. # Copyright (c) Digital Creations.  All rights reserved.
  5. # This license has been certified as Open Source(tm).
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are
  8. # met:
  9. # 1. Redistributions in source code must retain the above copyright
  10. #    notice, this list of conditions, and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. #    notice, this list of conditions, and the following disclaimer in
  13. #    the documentation and/or other materials provided with the
  14. #    distribution.
  15. # 3. Digital Creations requests that attribution be given to Zope
  16. #    in any manner possible. Zope includes a "Powered by Zope"
  17. #    button that is installed by default. While it is not a license
  18. #    violation to remove this button, it is requested that the
  19. #    attribution remain. A significant investment has been put
  20. #    into Zope, and this effort will continue if the Zope community
  21. #    continues to grow. This is one way to assure that growth.
  22. # 4. All advertising materials and documentation mentioning
  23. #    features derived from or use of this software must display
  24. #    the following acknowledgement:
  25. #      "This product includes software developed by Digital Creations
  26. #      for use in the Z Object Publishing Environment
  27. #      (http://www.zope.org/)."
  28. #    In the event that the product being advertised includes an
  29. #    intact Zope distribution (with copyright and license included)
  30. #    then this clause is waived.
  31. # 5. Names associated with Zope or Digital Creations must not be used to
  32. #    endorse or promote products derived from this software without
  33. #    prior written permission from Digital Creations.
  34. # 6. Modified redistributions of any form whatsoever must retain
  35. #    the following acknowledgment:
  36. #      "This product includes software developed by Digital Creations
  37. #      for use in the Z Object Publishing Environment
  38. #      (http://www.zope.org/)."
  39. #    Intact (re-)distributions of any official Zope release do not
  40. #    require an external acknowledgement.
  41. # 7. Modifications are encouraged but must be packaged separately as
  42. #    patches to official Zope releases.  Distributions that do not
  43. #    clearly separate the patches from the original work must be clearly
  44. #    labeled as unofficial distributions.  Modifications which do not
  45. #    carry the name Zope may be packaged in any form, as long as they
  46. #    conform to all of the clauses above.
  47. # Disclaimer
  48. #   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
  49. #   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  50. #   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  51. #   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
  52. #   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  53. #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  54. #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  55. #   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  56. #   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  57. #   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  58. #   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  59. #   SUCH DAMAGE.
  60. # This software consists of contributions made by Digital Creations and
  61. # many individuals on behalf of Digital Creations.  Specific
  62. # attributions are listed in the accompanying credits file.
  63. ##############################################################################
  64. __doc__='''Generic Database adapter
  65.  
  66.  
  67. $Id: DA.py,v 1.86.12.5 2000/09/13 13:47:59 brian Exp $'''
  68. __version__='$Revision: 1.86.12.5 $'[11:-2]
  69.  
  70. import OFS.SimpleItem, Aqueduct, RDB
  71. import DocumentTemplate, marshal, md5, base64, Acquisition, os
  72. from Aqueduct import decodestring, parse
  73. from Aqueduct import custom_default_report, default_input_form
  74. from Globals import HTMLFile, MessageDialog
  75. from cStringIO import StringIO
  76. import sys, Globals, OFS.SimpleItem, AccessControl.Role
  77. from string import atoi, find, join, split
  78. import DocumentTemplate, sqlvar, sqltest, sqlgroup
  79. from time import time
  80. from zlib import compress, decompress
  81. from DateTime.DateTime import DateTime
  82. md5new=md5.new
  83. import ExtensionClass
  84. import DocumentTemplate.DT_Util
  85. from cPickle import dumps, loads
  86. from Results import Results
  87. from App.Extensions import getBrain
  88. from AccessControl import getSecurityManager
  89.  
  90. try: from IOBTree import Bucket
  91. except: Bucket=lambda:{}
  92.  
  93.  
  94. class nvSQL(DocumentTemplate.HTML):
  95.     # Non-validating SQL Template for use by SQLFiles.
  96.     commands={}
  97.     for k, v in DocumentTemplate.HTML.commands.items(): commands[k]=v
  98.     commands['sqlvar' ]=sqlvar.SQLVar
  99.     commands['sqltest']=sqltest.SQLTest
  100.     commands['sqlgroup' ]=sqlgroup.SQLGroup
  101.  
  102.     _proxy_roles=()
  103.  
  104.  
  105. class SQL(ExtensionClass.Base, nvSQL):
  106.     # Validating SQL template for Zope SQL Methods.
  107.  
  108.     def validate(self, inst, parent, name, value, md):
  109.         return getSecurityManager().validate(inst, parent, name, value)
  110.  
  111. class DA(
  112.     Aqueduct.BaseQuery,Acquisition.Implicit,
  113.     Globals.Persistent,
  114.     AccessControl.Role.RoleManager,
  115.     OFS.SimpleItem.Item,
  116.     ):
  117.     'Database Adapter'
  118.  
  119.     _col=None
  120.     max_rows_=1000
  121.     cache_time_=0
  122.     max_cache_=100
  123.     class_name_=class_file_=''
  124.     _zclass=None
  125.     allow_simple_one_argument_traversal=None
  126.     template_class=SQL
  127.     
  128.     manage_options=(
  129.         (
  130.         {'label':'Edit', 'action':'manage_main',
  131.          'help':('ZSQLMethods','Z-SQL-Method_Edit.stx')},
  132.         {'label':'Test', 'action':'manage_testForm',
  133.          'help':('ZSQLMethods','Z-SQL-Method_Test.stx')},
  134.         {'label':'Advanced', 'action':'manage_advancedForm',
  135.          'help':('ZSQLMethods','Z-SQL-Method_Advanced.stx')},
  136.         )
  137.         +AccessControl.Role.RoleManager.manage_options
  138.         +OFS.SimpleItem.Item.manage_options
  139.         )
  140.  
  141.     # Specify how individual operations add up to "permissions":
  142.     __ac_permissions__=(
  143.         ('View management screens',
  144.          (
  145.         'manage_main', 'index_html',
  146.         'manage_advancedForm', 'PrincipiaSearchSource'
  147.         )),
  148.         ('Change Database Methods',
  149.          ('manage_edit','manage_advanced', 'manage_testForm','manage_test',
  150.           'manage_product_zclass_info')),
  151.         ('Use Database Methods', ('__call__',''), ('Anonymous','Manager')),
  152.         )
  153.    
  154.  
  155.     def __init__(self, id, title, connection_id, arguments, template):
  156.         self.id=str(id)
  157.         self.manage_edit(title, connection_id, arguments, template)
  158.     
  159.     manage_advancedForm=HTMLFile('advanced', globals())
  160.  
  161.     test_url___roles__=None
  162.     def test_url_(self):
  163.         'Method for testing server connection information'
  164.         return 'PING'
  165.  
  166.     _size_changes={
  167.         'Bigger': (5,5),
  168.         'Smaller': (-5,-5),
  169.         'Narrower': (0,-5),
  170.         'Wider': (0,5),
  171.         'Taller': (5,0),
  172.         'Shorter': (-5,0),
  173.         }
  174.  
  175.     def _er(self,title,connection_id,arguments,template,
  176.             SUBMIT,sql_pref__cols,sql_pref__rows,REQUEST):
  177.         dr,dc = self._size_changes[SUBMIT]
  178.         
  179.         rows=max(1,atoi(sql_pref__rows)+dr)
  180.         cols=max(40,atoi(sql_pref__cols)+dc)
  181.         e=(DateTime('GMT') + 365).rfc822()
  182.         resp=REQUEST['RESPONSE']
  183.         resp.setCookie('sql_pref__rows',str(rows),path='/',expires=e)
  184.         resp.setCookie('sql_pref__cols',str(cols),path='/',expires=e)
  185.         return self.manage_main(
  186.             self,REQUEST,
  187.             title=title,
  188.             arguments_src=arguments,
  189.             connection_id=connection_id,
  190.             src=template,
  191.             sql_pref__cols=cols,sql_pref__rows=rows)
  192.  
  193.     def manage_edit(self,title,connection_id,arguments,template,
  194.                     SUBMIT='Change',sql_pref__cols='50', sql_pref__rows='20',
  195.                     REQUEST=None):
  196.         """Change database method  properties
  197.  
  198.         The 'connection_id' argument is the id of a database connection
  199.         that resides in the current folder or in a folder above the
  200.         current folder.  The database should understand SQL.
  201.  
  202.         The 'arguments' argument is a string containing an arguments
  203.         specification, as would be given in the SQL method cration form.
  204.  
  205.         The 'template' argument is a string containing the source for the
  206.         SQL Template.
  207.         """
  208.  
  209.         if self._size_changes.has_key(SUBMIT):
  210.             return self._er(title,connection_id,arguments,template,
  211.                             SUBMIT,sql_pref__cols,sql_pref__rows,REQUEST)
  212.  
  213.         self.title=str(title)
  214.         self.connection_id=str(connection_id)
  215.         arguments=str(arguments)
  216.         self.arguments_src=arguments
  217.         self._arg=parse(arguments)
  218.         template=str(template)
  219.         self.src=template
  220.         self.template=t=self.template_class(template)
  221.         t.cook()
  222.         self._v_cache={}, Bucket()
  223.         if REQUEST:
  224.             if SUBMIT=='Change and Test':
  225.                 return self.manage_testForm(REQUEST)
  226.             message='ZSQL Method content changed'
  227.             return self.manage_main(self, REQUEST, manage_tabs_message=message)
  228.         return ''
  229.  
  230.  
  231.     def manage_advanced(self, max_rows, max_cache, cache_time,
  232.                         class_name, class_file, direct=None,
  233.                         REQUEST=None, zclass=''):
  234.         """Change advanced properties
  235.  
  236.         The arguments are:
  237.  
  238.         max_rows -- The maximum number of rows to be returned from a query.
  239.  
  240.         max_cache -- The maximum number of results to cache
  241.  
  242.         cache_time -- The maximum amound of time to use a cached result.
  243.  
  244.         class_name -- The name of a class that provides additional
  245.           attributes for result record objects. This class will be a
  246.           base class of the result record class.
  247.  
  248.         class_file -- The name of the file containing the class
  249.           definition.
  250.  
  251.         The class file normally resides in the 'Extensions'
  252.         directory, however, the file name may have a prefix of
  253.         'product.', indicating that it should be found in a product
  254.         directory.
  255.  
  256.         For example, if the class file is: 'ACMEWidgets.foo', then an
  257.         attempt will first be made to use the file
  258.         'lib/python/Products/ACMEWidgets/Extensions/foo.py'. If this
  259.         failes, then the file 'Extensions/ACMEWidgets.foo.py' will be
  260.         used.
  261.   
  262.         """
  263.         # paranoid type checking
  264.         if type(max_rows) is not type(1):
  265.             max_rows=atoi(max_rows)
  266.         if type(max_cache) is not type(1):
  267.             max_cache=atoi(max_cache)
  268.         if type(cache_time) is not type(1):
  269.             cache_time=atoi(cache_time)            
  270.         class_name=str(class_name)
  271.         class_file=str(class_file)
  272.         
  273.         self.max_rows_ = max_rows
  274.         self.max_cache_, self.cache_time_ = max_cache, cache_time
  275.         self._v_cache={}, Bucket()
  276.         self.class_name_, self.class_file_ = class_name, class_file
  277.         self._v_brain=getBrain(self.class_file_, self.class_name_, 1)
  278.         self.allow_simple_one_argument_traversal=direct
  279.  
  280.         if zclass:
  281.             for d in self.aq_acquire('_getProductRegistryData')('zclasses'):
  282.                 if ("%s/%s" % (d.get('product'),d.get('id'))) == zclass:
  283.                     self._zclass=d['meta_class']
  284.                     break
  285.  
  286.         
  287.         if REQUEST is not None:
  288.             m="ZSQL Method advanced settings have been set"
  289.             return self.manage_advancedForm(self,REQUEST,manage_tabs_message=m)
  290. ##            return self.manage_editedDialog(REQUEST)
  291.  
  292.     #def getFindContent(self):
  293.     #    """Return content for use by the Find machinery."""
  294.     #    return '%s\n%s' % (self.arguments_src, self.src)
  295.  
  296.     def PrincipiaSearchSource(self):
  297.         """Return content for use by the Find machinery."""
  298.         return '%s\n%s' % (self.arguments_src, self.src)
  299.     
  300.     def manage_testForm(self, REQUEST):
  301.         " "
  302.         input_src=default_input_form(self.title_or_id(),
  303.                                      self._arg, 'manage_test',
  304.                                      '<dtml-var manage_tabs>')
  305.         return DocumentTemplate.HTML(input_src)(self, REQUEST, HTTP_REFERER='')
  306.  
  307.     def manage_test(self, REQUEST):
  308.         """Test an SQL method."""
  309.         # Try to render the query template first so that the rendered
  310.         # source will be available for the error message in case some
  311.         # error occurs...
  312.         try:    src=self(REQUEST, src__=1)
  313.         except: src="Could not render the query template!"
  314.         result=()
  315.         t=v=tb=None
  316.         try:
  317.             try:
  318.                 src, result=self(REQUEST, test__=1)
  319.                 if find(src,'\0'):
  320.                     src=join(split(src,'\0'),'\n'+'-'*60+'\n')
  321.                 if result._searchable_result_columns():
  322.                     r=custom_default_report(self.id, result)
  323.                 else:
  324.                     r='This statement returned no results.'
  325.             except:
  326.                 t, v, tb = sys.exc_info()
  327.                 r='<strong>Error, <em>%s</em>:</strong> %s' % (t, v)
  328.  
  329.             report=DocumentTemplate.HTML(
  330.                 '<html>\n'
  331.                 '<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">\n'
  332.                 '<dtml-var manage_tabs>\n<hr>\n%s\n\n'
  333.                 '<hr><strong>SQL used:</strong><br>\n<pre>\n%s\n</pre>\n<hr>\n'
  334.                 '</body></html>'
  335.                 % (r,src))
  336.  
  337.             report=apply(report,(self,REQUEST),{self.id:result})
  338.  
  339.             if tb is not None:
  340.                 self.raise_standardErrorMessage(
  341.                     None, REQUEST, t, v, tb, None, report)
  342.  
  343.             return report
  344.         
  345.         finally: tb=None
  346.  
  347.     def index_html(self, URL1):
  348.         " "
  349.         raise 'Redirect', ("%s/manage_testForm" % URL1)
  350.  
  351.     def _searchable_arguments(self): return self._arg
  352.  
  353.     def _searchable_result_columns(self): return self._col
  354.  
  355.     def _cached_result(self, DB__, query):
  356.  
  357.         # Try to fetch from cache
  358.         if hasattr(self,'_v_cache'): cache=self._v_cache
  359.         else: cache=self._v_cache={}, Bucket()
  360.         cache, tcache = cache
  361.         max_cache=self.max_cache_
  362.         now=time()
  363.         t=now-self.cache_time_
  364.         if len(cache) > max_cache / 2:
  365.             keys=tcache.keys()
  366.             keys.reverse()
  367.             while keys and (len(keys) > max_cache or keys[-1] < t):
  368.                 key=keys[-1]
  369.                 q=tcache[key]
  370.                 del tcache[key]
  371.                 del cache[q]
  372.                 del keys[-1]
  373.                 
  374.         if cache.has_key(query):
  375.             k, r = cache[query]
  376.             if k > t: return r
  377.  
  378.         result=apply(DB__.query, query)
  379.         if self.cache_time_ > 0:
  380.             tcache[int(now)]=query
  381.             cache[query]= now, result
  382.  
  383.         return result
  384.  
  385.     def __call__(self, REQUEST=None, __ick__=None, src__=0, test__=0, **kw):
  386.         """Call the database method
  387.  
  388.         The arguments to the method should be passed via keyword
  389.         arguments, or in a single mapping object. If no arguments are
  390.         given, and if the method was invoked through the Web, then the
  391.         method will try to acquire and use the Web REQUEST object as
  392.         the argument mapping.
  393.  
  394.         The returned value is a sequence of record objects.
  395.         """
  396.  
  397.         if REQUEST is None:
  398.             if kw: REQUEST=kw
  399.             else:
  400.                 if hasattr(self, 'REQUEST'): REQUEST=self.REQUEST
  401.                 else: REQUEST={}
  402.  
  403.         try: dbc=getattr(self, self.connection_id)
  404.         except AttributeError:
  405.             raise AttributeError, (
  406.                 "The database connection <em>%s</em> cannot be found." % (
  407.                 self.connection_id))
  408.  
  409.         try: DB__=dbc()
  410.         except: raise 'Database Error', (
  411.             '%s is not connected to a database' % self.id)
  412.         
  413.         if hasattr(self, 'aq_parent'):
  414.             p=self.aq_parent
  415.             if self._isBeingAccessedAsZClassDefinedInstanceMethod():
  416.                 p=p.aq_parent
  417.         else: p=None
  418.  
  419.         argdata=self._argdata(REQUEST)
  420.         argdata['sql_delimiter']='\0'
  421.         argdata['sql_quote__']=dbc.sql_quote__
  422.  
  423.         security=getSecurityManager()
  424.         security.addContext(self)
  425.         try:     query=apply(self.template, (p,), argdata)
  426.         finally: security.removeContext(self)
  427.  
  428.         if src__: return query
  429.  
  430.         if self.cache_time_ > 0 and self.max_cache_ > 0:
  431.             result=self._cached_result(DB__, (query, self.max_rows_))
  432.         else: result=DB__.query(query, self.max_rows_)
  433.  
  434.         if hasattr(self, '_v_brain'): brain=self._v_brain
  435.         else:
  436.             brain=self._v_brain=getBrain(self.class_file_, self.class_name_)
  437.  
  438.         zc=self._zclass
  439.         if zc is not None: zc=zc._zclass_
  440.  
  441.         if type(result) is type(''):
  442.             f=StringIO()
  443.             f.write(result)
  444.             f.seek(0)
  445.             result=RDB.File(f,brain,p, zc)
  446.         else:
  447.             result=Results(result, brain, p, zc)
  448.         columns=result._searchable_result_columns()
  449.         if test__ and columns != self._col: self._col=columns
  450.  
  451.         # If run in test mode, return both the query and results so
  452.         # that the template doesn't have to be rendered twice!
  453.         if test__: return query, result
  454.  
  455.         return result
  456.  
  457.     def da_has_single_argument(self): return len(self._arg)==1
  458.  
  459.     def __getitem__(self, key):
  460.         args=self._arg
  461.         if self.allow_simple_one_argument_traversal and len(args)==1:
  462.             results=self({args.keys()[0]: key})
  463.             if results:
  464.                 if len(results) > 1: raise KeyError, key
  465.             else: raise KeyError, key
  466.             r=results[0]
  467.             # if hasattr(self, 'aq_parent'): r=r.__of__(self.aq_parent)
  468.             return r
  469.             
  470.         self._arg[key] # raise KeyError if not an arg
  471.         return Traverse(self,{},key)
  472.  
  473.     def connectionIsValid(self):
  474.         return (hasattr(self, self.connection_id) and
  475.                 hasattr(getattr(self, self.connection_id), 'connected'))
  476.  
  477.     def connected(self):
  478.         return getattr(getattr(self, self.connection_id), 'connected')()
  479.  
  480.  
  481.     def manage_product_zclass_info(self):
  482.         r=[]
  483.         Z=self._zclass
  484.         Z=getattr(Z, 'aq_self', Z)
  485.         for d in self.aq_acquire('_getProductRegistryData')('zclasses'):
  486.             z=d['meta_class']
  487.             if hasattr(z._zclass_,'_p_deactivate'):
  488.                 # Eek, persistent
  489.                 continue
  490.             x={}
  491.             x.update(d)
  492.             x['selected'] = (z is Z) and 'selected' or ''
  493.             del x['meta_class']
  494.             r.append(x)
  495.             
  496.         return r 
  497.  
  498.  
  499.  
  500. Globals.default__class_init__(DA)
  501.  
  502.  
  503.  
  504. ListType=type([])
  505. class Traverse(ExtensionClass.Base):
  506.     """Helper class for 'traversing' searches during URL traversal
  507.     """
  508.     _r=None
  509.     _da=None
  510.  
  511.     def __init__(self, da, args, name=None):
  512.         self._da=da
  513.         self._args=args
  514.         self._name=name
  515.  
  516.     def __bobo_traverse__(self, REQUEST, key):
  517.         name=self._name
  518.         da=self.__dict__['_da']
  519.         args=self._args
  520.         if name:
  521.             if args.has_key(name):
  522.                 v=args[name]
  523.                 if type(v) is not ListType: v=[v]
  524.                 v.append(key)
  525.                 key=v
  526.  
  527.             args[name]=key
  528.  
  529.             if len(args) < len(da._arg):            
  530.                 return self.__class__(da, args)
  531.             key=self # "consume" key
  532.  
  533.         elif da._arg.has_key(key): return self.__class__(da, args, key)
  534.  
  535.         results=da(args)
  536.         if results:
  537.             if len(results) > 1:
  538.                 try: return results[atoi(key)].__of__(da)
  539.                 except: raise KeyError, key
  540.         else: raise KeyError, key
  541.         r=results[0]
  542.         # if hasattr(da, 'aq_parent'): r=r.__of__(da.aq_parent)
  543.         self._r=r
  544.  
  545.         if key is self: return r
  546.  
  547.         if hasattr(r,'__bobo_traverse__'):
  548.             try: return r.__bobo_traverse__(REQUEST, key)
  549.             except: pass
  550.  
  551.         try: return getattr(r,key)
  552.         except AttributeError, v:
  553.             if str(v) != key: raise AttributeError, v
  554.  
  555.         return r[key]
  556.  
  557.     def __getattr__(self, name):
  558.         r=self.__dict__['_r']
  559.         if hasattr(r, name): return getattr(r,name)
  560.         return getattr(self.__dict__['_da'], name)
  561.  
  562.